#[macro_use]
extern crate tracing;

extern crate fs_extra;
extern crate tracing_appender;
extern crate tracing_subscriber;

use chrono::NaiveDate;
use chrono::NaiveDateTime;
use sscanf::sscanf;

use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::OnceLock;
use std::thread;
use std::time::Duration;

static MLOG_FOLDER: OnceLock<PathBuf> = OnceLock::new();
static mut MLOG_SIZE_MAX: u64 = 0;

// 在logs目录查找最大的index
fn mlog_folder_index_max(dir: &Path) -> std::io::Result<u64> {
    let mut log_index = 0;

    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let p = entry.path();
        let pn = p.file_name().unwrap().to_str().unwrap();

        if let Ok(index) = u64::from_str(pn) {
            if log_index < index {
                log_index = index;
            }
        }
    }

    Ok(log_index)
}

// 在logs目录查找最小的index
fn mlog_folder_index_min(dir: &Path) -> std::io::Result<u64> {
    let mut log_index = u64::MAX;

    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let p = entry.path();
        let pn = p.file_name().unwrap().to_str().unwrap();

        if let Ok(index) = u64::from_str(pn) {
            if log_index > index {
                log_index = index;
            }
        }
    }

    Ok(log_index)
}

// 获取日志文件夹里面文件的总大小，遍历目录，仅累加文件夹名是纯数字的文件夹总大小
fn mlog_size_get(mlog_folder: &PathBuf) -> std::io::Result<u64> {
    let mut mlog_size = 0;

    for entry in fs::read_dir(mlog_folder)? {
        let entry = entry?;

        trace!(
            "mlog_size_get entry: {:?} {:?} {:?}",
            entry,
            entry.path(),
            entry.file_name()
        );

        if let Ok(file_name) = entry.file_name().into_string() {
            if u64::from_str(&file_name).is_err() {
                continue;
            }
            let target_folder = entry.path();

            if let Ok(n) = fs_extra::dir::get_size(target_folder) {
                trace!("mlog_size_get count: {}", n);
                mlog_size += n;
            }
        }
    }

    // unsafe {
    //     println!("mlog_size_get MLOG_FOLDER {:?}", MLOG_FOLDER.get().unwrap());
    // }
    debug!("mlog_size_get mlog_size: {}", mlog_size);

    Ok(mlog_size)
}

// 解析日志文件名的时间，日志文件的文件名格式yyyy-MM-dd-HH-mm.log，可能只有年月日
// 旧格式是log.xxxx，没有后缀
fn mlog_date_parse_from_file_name(file_name: &str) -> Option<NaiveDateTime> {
    let sv: Vec<&str> = file_name.split(".").collect();

    let date_str;

    if sv.len() != 2 {
        return None;
    }

    if sv[0] == "log" {
        date_str = sv[1];
    } else if sv[1] == "log" {
        date_str = sv[0];
    } else {
        return None;
    }

    if let Ok((year, month, day)) = sscanf!(date_str, "{i32}-{u32}-{u32}") {
        return NaiveDate::from_ymd_opt(year, month, day)
            .unwrap()
            .and_hms_opt(0, 0, 0);
    }

    if let Ok((year, month, day, hour)) = sscanf!(date_str, "{i32}-{u32}-{u32}-{u32}") {
        return NaiveDate::from_ymd_opt(year, month, day)
            .unwrap()
            .and_hms_opt(hour, 0, 0);
    }

    if let Ok((year, month, day, hour, min)) = sscanf!(date_str, "{i32}-{u32}-{u32}-{u32}-{u32}") {
        return NaiveDate::from_ymd_opt(year, month, day)
            .unwrap()
            .and_hms_opt(hour, min, 0);
    }

    None
}

// 按文件夹名称和日志文件名的时间顺序，删除最早的一个日志文件
// 日志文件的文件名格式yyyy-MM-dd-HH-mm.log，可能没有分钟和小时
fn mlog_delete_earliest_log(index_folder: &Path) -> std::io::Result<Option<NaiveDateTime>> {
    let mut earliest_time: NaiveDateTime = NaiveDateTime::MAX;
    let mut delete_file_path: PathBuf = PathBuf::new();

    for entry in fs::read_dir(index_folder)? {
        let entry = entry?;

        let file_path = entry.path();
        let file_name = file_path.file_name().unwrap().to_str().unwrap();

        trace!(
            "mlog_delete_earliest_log file_path: {:?} file_name: {}",
            file_path,
            file_name
        );

        if let Some(dt) = mlog_date_parse_from_file_name(file_name) {
            trace!("mlog_delete_earliest_log dt: {:?}", dt);
            if earliest_time > dt {
                earliest_time = dt;
                delete_file_path = file_path;
            }
        }
    }

    if earliest_time != NaiveDateTime::MAX {
        info!(
            "mlog_delete_earliest_log earliest_time: {:?}",
            earliest_time
        );

        warn!(
            "mlog_delete_earliest_log delete file: {:?}",
            delete_file_path
        );
        fs::remove_file(delete_file_path)?;
        return Ok(Some(earliest_time));
    }

    Ok(None)
}

// 会强制删除目录
fn mlog_limit_size(mlog_folder: &PathBuf) -> std::io::Result<()> {
    while mlog_size_get(mlog_folder)? > unsafe { MLOG_SIZE_MAX } {
        loop {
            let index = mlog_folder_index_min(mlog_folder)?;
            let index_folder = mlog_folder.join(index.to_string());

            if let Ok(dt) = mlog_delete_earliest_log(&index_folder) {
                if dt.is_some() {
                    break;
                }

                // 日志目录过大，但是又没有日志文件被删除，则强制删除一个索引日志目录
                warn!("mlog_limit_size remove_dir_all: {:?}", index_folder);
                fs::remove_dir_all(index_folder)?;
                continue;
            }
        }
    }

    Ok(())
}

fn mlog_task() {
    let logs_folder = MLOG_FOLDER.get().unwrap();
    if let Err(e) = mlog_limit_size(logs_folder) {
        error!("mlog_limit_size: {}", e);
    }
}

pub fn mlog_init(
    logs_folder: &str,
    logs_size_max: u64,
    rotation: tracing_appender::rolling::Rotation,
) -> tracing_appender::non_blocking::WorkerGuard {
    let logs_folder = std::path::Path::new(logs_folder);

    if !logs_folder.exists() {
        println!("mlog_init create_dir {:?}", logs_folder);
        fs::create_dir(logs_folder).unwrap();
    }

    let logs_folder = fs::canonicalize(logs_folder).unwrap();

    println!("mlog_init logs_folder: {:?}", logs_folder);

    MLOG_FOLDER.set(logs_folder).unwrap();

    unsafe { MLOG_SIZE_MAX = logs_size_max };

    let index = mlog_folder_index_max(MLOG_FOLDER.get().unwrap()).unwrap_or(0) + 1;

    let index_folder = MLOG_FOLDER.get().unwrap().join(index.to_string());
    let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
        .rotation(rotation)
        // .filename_prefix("log")
        .filename_suffix("log") // log file names will be suffixed with `.log`
        .build(index_folder) // try to build an appender that stores log files in `/var/log`
        .expect("mlog_init initializing rolling file appender failed");

    let global_filter = tracing_subscriber::EnvFilter::from_default_env();
    // .add_directive("reqwest=info".parse().unwrap())
    // .add_directive("hyper=info".parse().unwrap());

    let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);

    tracing_subscriber::fmt()
        .with_writer(non_blocking)
        .with_ansi(false)
        .with_env_filter(global_filter)
        .init();

    thread::spawn(|| loop {
        // println!("mlog_task");
        mlog_task();
        thread::sleep(Duration::from_secs(5));
    });

    guard
}
